﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace UIControls.Utilities
{
    /// <summary>
    /// Renders using ghost script to jpeg, and does rotate automatically.
    /// </summary>
    class GSJpeggerAutoRotate : IEnumerable<FileInfo>, IDisposable
    {
        /// <summary>
        /// Get/Set the PS file that we are rendering
        /// </summary>
        public FileInfo PSFile { get; private set; }

        public DirectoryInfo TempFileLocation { get; private set; }

        /// <summary>
        /// Create the object with the file to render.
        /// </summary>
        /// <param name="psFile"></param>
        public GSJpeggerAutoRotate(FileInfo psFile)
        {
            PSFile = psFile;
        }

        /// <summary>
        /// Return the file infos!
        /// </summary>
        /// <returns></returns>
        IEnumerator<FileInfo> IEnumerable<FileInfo>.GetEnumerator()
        {
            bool first = true;
            bool doRotation = false;
            bool rotate90 = false;

            GSJpegger itr = new GSJpegger(PSFile);
            TempFileLocation = itr.TempDirectory;

            foreach (var f in itr)
            {
                ///
                /// Figure out from just the first image if we should be doing the rotation
                /// 

                if (first)
                {
                    first = false;

                    if (IsLandscape != Image.IsLandscape(f))
                    {
                        doRotation = true;
                        rotate90 = !PSCreatorIs("dvips");
                    }
                }
                   
                ///
                /// Do the rotations if we have to
                /// 

                if (doRotation) {
                    Image.RotateInPlace(f, rotate90);
                }

                ///
                /// Let the dude work on it
                /// 

                yield return f;
            }
        }

        /// <summary>
        /// Get the same iterator
        /// </summary>
        /// <returns></returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            foreach (var f in this)
            {
                yield return f;
            }
        }

        /// <summary>
        /// Look at the creator line in the postscript. Used for special case code. :-)
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        private bool PSCreatorIs(string p)
        {
            using (var itr = PSFile.GetLineIterator())
            {
                var psCommentLines = itr.TakeWhile(ln => !ln.Contains("%%EndComments"));
                var dvilines = from l in psCommentLines
                               where l.Contains("Creator")
                               select l;
                if (dvilines.Count() > 0)
                {
                    return dvilines.First().Contains("dvips");
                }
            }

            return false;
        }


        /// <summary>
        /// Cache the value for the orientation of the file. It takes some effort to
        /// figure out what way it should be set, so we save the result.
        /// </summary>
        bool? _isLandscapeCache = null;

        /// <summary>
        /// Internal code to determine if the thing is in landspace or in portrate. The problem is that
        /// post-script doesn't always get it right - or, I should say, that gs doesn't always get it right.
        ///
        /// We look for two clues. First, the BoundingBox - if that has x longer than y, then
        /// we can "assume" we are dealing with a landscaped file. If the Orientation comment exists,
        /// and is marked landscape, we again make the same assumption.
        /// 
        /// </summary>
        private bool IsLandscape
        {
            get
            {
                if (_isLandscapeCache.HasValue)
                {
                    return _isLandscapeCache.Value;
                }

                using (var itr = PSFile.GetLineIterator())
                {
                    var psCommentLines = itr.TakeWhile(ln => !ln.Contains("%%EndComments"));
                    var orientLines = from l in psCommentLines
                                      where l.Contains("Orientation") || l.Contains("BoundingBox")
                                      select l;

                    bool isLandscapeBB = false;
                    bool isLandscapeOr = false;

                    foreach (var l in orientLines)
                    {
                        if (l.Contains("Orientation") && l.Contains("Landscape"))
                        {
                            isLandscapeOr = true;
                        }
                        if (l.Contains("BoundingBox"))
                        {
                            var splitinfo = l.Split(' ');
                            var allints = from i in l.Split(' ')
                                          where i.Length > 0 && isOnlyNumbers(i)
                                          select Convert.ToInt32(i);

                            var allintsa = allints.ToArray();
                            if (allintsa.Length == 4)
                            {
                                int deltax = allintsa[2] - allintsa[0];
                                int deltay = allintsa[3] - allintsa[1];

                                isLandscapeBB = deltax > deltay;
                            }
                        }
                    }

                    _isLandscapeCache = isLandscapeBB || isLandscapeOr;
                    return _isLandscapeCache.Value;
                }
            }
        }

        /// <summary>
        /// Returns true if only the integer numbers are in this string.
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        private bool isOnlyNumbers(string i)
        {
            var badchars = from c in i
                           where !char.IsDigit(c)
                           select c;
            return badchars.Count() == 0;
        }

        #region IDisposable Members

        /// <summary>
        /// This is here to make sure the object gets cleaned up correctly.
        /// </summary>
        public void Dispose()
        {
        }

        #endregion
    }
}
